<template>
    <!-- 游戏容器的外层 div -->
    <div class="game-container">
      <!-- 显示游戏状态的 div，包括得分、蛇的长度和剩余生命 -->
      <div class="status">
        得分: {{ score }} | 长度: {{ snake.length }} | 剩余生命: ❤️{{ lives }}
      </div>
  
      <!-- 游戏棋盘的 div，根据计算属性设置样式 -->
      <div class="game-board" :style="boardStyle">
        <div 
          v-for="(segment, index) in snake" 
          :key="index"
          class="snake-segment"
          :style="getSegmentStyle(segment)"
        ></div>
        <!-- 显示食物的 div，根据计算属性设置位置 -->
        <div class="food" :style="foodPosition">🍎</div>
      </div>
  
      <!-- 游戏控制按钮的 div -->
      <div class="controls">
        <!-- 开始新游戏的按钮，点击时调用 startGame 方法 -->
        <button @click="startGame">新游戏</button>
        <!-- 方向键按钮的 div -->
        <div class="arrows">
          <!-- 监听点击事件，当用户点击对应的箭头按钮后，跳转到对应的函数处理 -->
          <button @click="changeDirection('up')">↑</button>
          <button @click="changeDirection('left')">←</button>
          <button @click="changeDirection('right')">→</button>
          <button @click="changeDirection('down')">↓</button>
        </div>
      </div>
    </div>
  </template>
  
  <script>
  // 从 Vue 中导入需要的响应式 API 和生命周期钩子函数
  import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
  
  export default {
    setup() {
      // ============= 状态声明 =============
      // 棋盘的网格大小，使用 ref 创建响应式数据
      const gridSize = ref(20)
      // 蛇的身体部分，使用 reactive 创建响应式数组对象
      const snake = reactive([{ x: 5, y: 5 }])
      // 食物的位置，使用 reactive 创建响应式对象，初始化位置
      const food = reactive({ x: 10, y: 10 }) 
      // 蛇的移动方向，使用 ref 创建响应式数据
      const direction = ref('right')
      // 玩家的得分，使用 ref 创建响应式数据
      const score = ref(0)
      // 玩家的剩余生命，使用 ref 创建响应式数据
      const lives = ref(3)
      // 游戏循环的定时器 ID，使用 ref 创建响应式数据
      const gameLoop = ref(null)
  
      // ============= 计算属性 =============
      // 计算游戏棋盘的样式，返回一个对象，包含宽度、高度、边框等样式属性
      // 1. computed 函数的使用：computed 是 Vue 框架中提供的一个函数，它用于创建计算属性。计算属性是一种基于其他响应式数据（如响应式对象的属性、响应式数组等）的衍生状态。
      // 计算属性具有缓存机制，只有当它所依赖的响应式数据发生变化时，才会重新计算其值，否则会直接返回缓存的结果。这有助于提高性能，避免不必要的重复计算。
      // 2. 传递给 computed 函数的是一个箭头函数 () => ({ })。这个箭头函数的作用是定义计算属性 boardStyle 的计算逻辑。
      const boardStyle = computed(() => ({
        width: `${gridSize.value * 30}px`,
        height: `${gridSize.value * 30}px`,
        border: '2px solid #333',
        position: 'relative',
        margin: '20px auto'
      }))
  
      // 计算食物的位置样式，返回一个对象，包含食物的 left 和 top 位置属性
      // 1. computed 函数的使用：computed 是 Vue 框架中提供的一个函数，它用于创建计算属性。计算属性是一种基于其他响应式数据（如响应式对象的属性、响应式数组等）的衍生状态。
      // 计算属性具有缓存机制，只有当它所依赖的响应式数据发生变化时，才会重新计算其值，否则会直接返回缓存的结果。这有助于提高性能，避免不必要的重复计算。
      // 2. 传递给 computed 函数的是一个箭头函数 () => ({ left: \({food.x * 30}px`, top: `\){food.y * 30}px })。这个箭头函数的作用是定义计算属性 foodPosition 的计算逻辑。
      // 3. 计算属性 foodPosition 的返回值是一个包含 left 和 top 属性的对象，这个对象可以用于在 Vue 模板中绑定内联样式（例如通过 :style 指令），从而动态地设置食物在页面上的位置。当 food 对象的 x 或 y 属性发生变化时（因为 food 是响应式的），foodPosition 计算属性会自动重新计算，进而更新食物在页面上的显示位置。
      // 4. ${} 是 JavaScript 中模板字符串的语法。在模板字符串中，${} 被用来嵌入表达式。具体来说，你可以把任意有效的 JavaScript 表达式放在 ${} 内部，JavaScript 会先计算该表达式的值，然后将其结果插入到模板字符串里对应的位置。
      const foodPosition = computed(() => ({
        left: `${food.x * 30}px`,
        top: `${food.y * 30}px`
      }))
  
      // ============= 游戏方法 =============
      // 生成食物的方法，随机设置食物的位置并确保不在蛇身上
      const spawnFood = () => {
        food.x = Math.floor(Math.random() * gridSize.value)
        food.y = Math.floor(Math.random() * gridSize.value)
        
        // 确保食物不会出现在蛇身上
        while (snake.some(seg => seg.x === food.x && seg.y === food.y)) {
          food.x = Math.floor(Math.random() * gridSize.value)
          food.y = Math.floor(Math.random() * gridSize.value)
        }
      }
  
      // 检查碰撞的方法，判断传入的位置是否与边界或蛇自身碰撞
      const checkCollision = (pos) => {
        return (
          pos.x < 0 || 
          pos.x >= gridSize.value || 
          pos.y < 0 || 
          pos.y >= gridSize.value ||
          snake.some(seg => seg.x === pos.x && seg.y === pos.y)
        )
      }
  
      // 移动蛇的方法，根据当前方向移动蛇头，处理碰撞、吃食物等逻辑
      const move = () => {
        // 创建一个新的对象 head，该对象是 snake[0] 对象的浅拷贝。
        // snake 是一个数组，用来存储蛇身体各部分的位置信息，每个元素都是一个包含 x 和 y 坐标属性的对象，代表蛇身体某一段在游戏网格中的位置。
        // snake[0] 表示蛇的头部，也就是数组中的第一个元素。
        // ... 是对象展开语法（ES6 引入），它会把 snake[0] 对象的所有属性复制到一个新的对象中。
        // const head = { ...snake[0] } 整体意思是创建一个新的对象 head，并将 snake[0] 的所有属性复制给它。
        const head = { ...snake[0] }
        
        switch(direction.value) {
          case 'up': head.y--; break
          case 'down': head.y++; break
          case 'left': head.x--; break
          case 'right': head.x++; break
        }
  
        if (checkCollision(head)) {
          lives.value--
          if (lives.value <= 0) {
            alert(`游戏结束！最终得分: ${score.value}`)
            startGame()
          }
          return
        }
 
        if (head.x === food.x && head.y === food.y) {
          score.value += 10
          spawnFood()
        } else {
          snake.pop()
        }
  
        snake.unshift(head)
      }
  
      // 开始游戏的方法，重置游戏状态并启动游戏循环
      const startGame = () => {
        clearInterval(gameLoop.value)
        snake.splice(0, snake.length, { x: 5, y: 5 })
        direction.value = 'right'
        score.value = 0
        lives.value = 3
        spawnFood()
        gameLoop.value = setInterval(move, 200)
      }
  
      // 改变蛇移动方向的方法，确保不会反向移动
      const changeDirection = (newDir) => {
        const opposite = {
          up: 'down', down: 'up',
          left: 'right', right: 'left'
        }
        if (newDir !== opposite[direction.value]) {
          direction.value = newDir
        }
      }
  
      // 获取蛇每一段样式的方法，返回一个对象，包含位置、背景颜色等样式属性
      const getSegmentStyle = (segment) => ({
        left: `${segment.x * 30}px`,
        top: `${segment.y * 30}px`,
        background: `hsl(${score.value * 5}, 70%, 50%)`,
        width: '28px',
        height: '28px',
        position: 'absolute',
        transition: 'all 0.1s'
      })
  
      // ============= 生命周期 =============
      // 组件挂载完成后的钩子函数，启动游戏并绑定键盘事件
      onMounted(() => {
        startGame()
        
        window.addEventListener('keydown', (e) => {
          const dirMap = {
            ArrowUp: 'up',
            ArrowDown: 'down',
            ArrowLeft: 'left',
            ArrowRight: 'right'
          }
          if (dirMap[e.key]) changeDirection(dirMap[e.key])
        })
      })
  
      // 组件卸载前的钩子函数，清除游戏循环定时器
      onBeforeUnmount(() => {
        clearInterval(gameLoop.value)
      })
  
      // ============= 暴露给模板 =============
      // 将需要在模板中使用的状态、计算属性和方法返回
      return {
        // 状态
        snake,
        score,
        lives,
        food,
        // 计算属性
        boardStyle,
        foodPosition,
        // 方法
        startGame,
        changeDirection,
        getSegmentStyle
      }
    }
  }
  </script>
  
  <style scoped>
  /* 游戏容器的样式，设置字体和文本对齐方式 */
  .game-container {
    font-family: Arial, sans-serif;
    text-align: center;
  }
  
  /* 游戏状态显示区域的样式，设置底部外边距和字体大小 */
  .status {
    margin-bottom: 20px;
    font-size: 1.2em;
  }
  
  /* 游戏控制按钮区域的样式，设置顶部外边距 */
  .controls {
    margin-top: 20px;
  }
  
  /* 按钮的样式，设置内边距、外边距、字体大小和鼠标指针样式 */
  button {
    padding: 10px 15px;
    margin: 0 5px;
    font-size: 1em;
    cursor: pointer;
  }
  
  /* 食物的样式，设置绝对定位、字体大小和过渡效果 */
  .food {
    position: absolute;
    font-size: 24px;
    transition: all 0.3s;
  }
  </style>